﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.IO;
using System.Threading;

namespace CPGM
{
    public class CPGMSender
    {
        private Socket s;
        private string _localIp;


        private List<CPGMPacket> packets;


        //TODO: Split the file into chunks of bytes
        //TODO: Keep the ID of each chunk
        //TODO: Answer a NAK
        public void SplitFile(string path)
        {
            FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read);
            packets = new List<CPGMPacket>();
            int id = 0;
            //TODO: split the file into chunks of bytes
            using (MemoryStream ms = new MemoryStream())
            {
                byte[] buffer = new byte[1024];
                UTF8Encoding temp = new UTF8Encoding(true);
                int read = 0;
                while ((read = fs.Read(buffer, 0, buffer.Length)) > 0)
                {
                    ms.Write(buffer, 0, read);
                    CPGMPacket pack = new CPGMPacket();
                    pack.Data = buffer;
                    pack.ID = id;
                    packets.Add(pack);
                    id++;
                }
            }
            packets[packets.Count - 1].IsLast = true;
            //TODO: for each packet in packages send the expected_id missed and sleep
            foreach (CPGMPacket packs in packets)
            {
                SendExpectedPack(packs);
                //Thread.Sleep(2000);
            }

        }


        /// <summary>
        /// Indicates if the CPGMSender is Connnected
        /// </summary>
        public bool IsConnected
        {
            get
            {
                return s != null ? s.Connected : false;
            }
        }

        /// <summary>
        /// Connects the CPGMSender
        /// </summary>
        /// <param name="multicastGroup">The IP for the Multicast Group</param>
        /// <param name="portNumber">The port number</param>
        /// <param name="TTL">The TTL port</param>
        /// <returns>The operation succeed or not</returns>
        public bool Connect(string multicastGroup, string portNumber, string TTL)
        {
            bool result = true;
            IPAddress ip;
            if (!IsConnected)
            {
                ip = IPAddress.Parse(multicastGroup);

                s = new Socket(AddressFamily.InterNetwork,
                                SocketType.Dgram, ProtocolType.Udp);

                s.SetSocketOption(SocketOptionLevel.IP,
                    SocketOptionName.AddMembership, new MulticastOption(ip));

                s.SetSocketOption(SocketOptionLevel.IP,
                    SocketOptionName.MulticastTimeToLive, int.Parse(TTL));

                IPEndPoint ipep = new IPEndPoint(IPAddress.Parse(multicastGroup), int.Parse(portNumber));

                s.Connect(ipep);

                _localIp = GetLocalIP();
            }
            return result;
        }

        public bool Connect(string multicastGroup, string portNumber, string TTL, string NAKPortNumber)
        {
            IPEndPoint e = new IPEndPoint(IPAddress.Any, int.Parse(NAKPortNumber));
            UdpClient nak_cl = new UdpClient(e);

            UdpState s = new UdpState();
            s.client = nak_cl;
            s.endPoint = e;
            IPEndPoint p = new IPEndPoint(IPAddress.Any, 662);
            nak_cl.Connect(p);

            nak_cl.BeginReceive(new AsyncCallback(NAK_Received), s);

            return Connect(multicastGroup, portNumber, TTL);

        }

        /// <summary>
        /// Disconnects the CPGMSender
        /// </summary>
        /// <returns>The operation succeed or not</returns>
        public bool Disconnect()
        {
            bool result = true;
            if (IsConnected)
                s.Close();
            return result;
        }

        /// <summary>
        /// Sends the content
        /// </summary>
        /// <param name="bArray">Content to be sent</param>
        public void Send(byte[] bArray)
        {
            CPGMPacket pack = new CPGMPacket();
            pack.Data = bArray;
            pack.LocalIPAddress = _localIp;
            byte[] temp = pack.ConvertToByteArray();
            s.Send(temp, temp.Length, SocketFlags.None);
        }


        public void SendExpectedPack(CPGMPacket pack)
        {
            pack.LocalIPAddress = _localIp;
            byte[] temp = pack.ConvertToByteArray();
            s.Send(temp, temp.Length, SocketFlags.None);
        }

        //
        public void NAK_Received(IAsyncResult ar)
        {
            UdpClient u = (UdpClient)((UdpState)(ar.AsyncState)).client;
            IPEndPoint e = (IPEndPoint)((UdpState)(ar.AsyncState)).endPoint;

            byte[] array = u.EndReceive(ar, ref e);
            string receiveString = Encoding.ASCII.GetString(array);

            foreach (CPGMPacket packs in packets)
            {
                if (packs.ID.ToString() == receiveString)
                {
                    SendExpectedPack(packs);
                }
            }
        }

        private string GetLocalIP()
        {
            IPHostEntry host;
            string localIP = "?";
            host = Dns.GetHostEntry(Dns.GetHostName());
            foreach (IPAddress ip in host.AddressList)
            {
                if (ip.AddressFamily == AddressFamily.InterNetwork)
                {
                    localIP = ip.ToString();
                }
            }
            return localIP;
        }

    }

    public class UdpState
    {
        public IPEndPoint endPoint { get; set; }

        public UdpClient client { get; set; }
    }
}
